from . import exam_blueprint
from ..utils.response_util import response
from ..utils.exception import APIException
from ..utils.error_code import *
from ..utils.check import *
from ..models import *

from flask import request, send_file, url_for

import pandas as pd
from io import BytesIO
import datetime
import json
import random
from math import ceil


@exam_blueprint.route("/", methods=["POST", "GET"])
def exam_main():
    return "Exam"


@exam_blueprint.errorhandler(APIException)
def exam_error(e):
    return APIException.to_dict(e)


@exam_blueprint.route("/list", methods=["POST"])
@user_is_login
def exam_list():
    """
    获取考试列表
    :return:
    """
    try:
        current_page = int(request.args.get("currentPage"))
    except:
        current_page = 1
    data_per_page = 10
    username = session.get("username")
    user = User.query.filter(User.username == username).limit(1).first()
    r = list()

    if user.user_type == 1:
        exams = Exam.query.filter_by(status=True).order_by(-Exam.create_time).all()
        list_len = len(exams)
        for exam in exams:
            tmp = dict()
            tmp["exam_id"] = exam.exam_id
            tmp["exam_name"] = exam.exam_name
            tmp["paper_id"] = exam.paper_id
            tmp["exam_add_user_id"] = exam.exam_add_user_id
            tmp["start_time"] = str(exam.start_time)
            tmp["end_time"] = str(exam.end_time)
            tmp["state"] = "Open" if exam.start_time <= datetime.datetime.now() <= exam.end_time else "Ended"
            tmp["status"] = 1
            r.append(tmp)
    else:
        exams = Exam.query.order_by(-Exam.create_time).all()
        list_len = len(exams)
        for exam in exams:
            tmp = dict()
            tmp["exam_id"] = exam.exam_id
            tmp["exam_name"] = exam.exam_name
            tmp["paper_id"] = exam.paper_id
            tmp["exam_add_user_id"] = exam.exam_add_user_id
            tmp["start_time"] = str(exam.start_time)
            tmp["end_time"] = str(exam.end_time)
            tmp["state"] = "Open" if exam.start_time <= datetime.datetime.now() <= exam.end_time else "Ended"
            tmp["status"] = exam.status
            r.append(tmp)
    max_page_num = ceil(list_len / data_per_page)
    ret = {
        "currentPage": current_page,
        "maxPageNum": max_page_num,
        "num": list_len,
        "list": r[data_per_page * (current_page - 1): data_per_page * current_page]
    }

    return response(200, "Query Exam Success", ret)


@exam_blueprint.route("/add", methods=["POST"])
@user_is_login
@check_user_type_2
def exam_add():
    """
    添加考试
    :return:
    """
    data = get_data()
    username = session.get("username")
    exam_name = data.get("examName")
    paper_id = data.get("paperId")
    start_time = data.get("startTime")
    end_time = data.get("endTime")
    unlock_secret = data.get("unlockSecret")
    relate_course = data.get("relateCourse")
    exam_status = data.get("examStatus")

    paper = Paper.query.filter(Paper.paper_id == paper_id).limit(1).first()
    if paper is None:
        raise APIException(NO_PAPER_EXISTS)
    course = Course.query.filter(Course.course_id == relate_course).limit(1).first()
    if course is None:
        raise APIException(COURSE_NOT_EXISTS)
    # 生成 Exam
    exam = Exam(exam_name, paper_id, start_time, end_time, unlock_secret, relate_course, username)
    exam.status = exam_status
    try:
        db.session.add(exam)
        db.session.commit()
    except:
        db.session.rollback()
        raise APIException(ADD_ERROR)
    finally:
        db.session.close()

    return response(200, "Add Exam Success", {})


@exam_blueprint.route("/delete", methods=["POST"])
@user_is_login
@check_user_type_2
def exam_delete():
    """
    删除考试
    :return:
    """
    data = get_data()
    username = session.get("username")
    exam_id = data.get("examId")

    # 删除 Exam
    exam = Exam.query.filter(Exam.exam_id == exam_id).first()
    user = User.query.filter(User.username == username).first()
    if exam is None:
        raise APIException(NO_EXAM_EXISTS)
    if user.user_type == 2 and exam.exam_add_user_id != user.username:
        raise APIException(INSUFFICIENT_USER_PERMISSION)

    res = Exam.query.filter_by(exam_id=exam_id).delete()
    db.session.commit()
    db.session.close()

    if res == 1:
        return response(200, "Delete Exam Success", {"exam_id": exam_id})
    else:
        raise APIException(DELETE_ERROR)


@exam_blueprint.route("/update", methods=["POST"])
@user_is_login
@check_user_type_2
def exam_update():
    """
    修改考试
    :return:
    """
    data = get_data()
    username = session.get("username")
    exam_id = data.get("examId")
    exam_name = data.get("examName")
    paper_id = data.get("paperId")
    start_time = data.get("startTime")
    end_time = data.get("endTime")
    unlock_secret = data.get("unlockSecret")
    exam_status = data.get("examStatus")
    relate_course_id = data.get("relateCourseId")
    new_data = dict()

    user = User.query.filter(User.username == username).first()
    exam = Exam.query.filter(Exam.exam_id == exam_id).first()
    if exam is None:
        raise APIException(NO_EXAM_EXISTS)
    if exam.exam_add_user_id != int(username) and user.type == 2:
        raise APIException(INSUFFICIENT_USER_PERMISSION)
    # 修改 exam 数据
    if exam_name is not None:
        new_data["exam_name"] = exam_name
    if relate_course_id is not None:
        new_data["relate_course"] = relate_course_id
    if paper_id is not None:
        new_data["paper_id"] = paper_id
    if start_time is not None:
        new_data["start_time"] = start_time
    if end_time is not None:
        new_data["end_time"] = end_time
    if unlock_secret is not None:
        new_data["unlock_secret"] = unlock_secret
    if exam_status is not None:
        new_data["status"] = exam_status

    res = Exam.query.filter_by(exam_id=exam_id).update(new_data)
    db.session.commit()
    db.session.close()

    if res == 1:
        return response(200, "Update Exam Success", {"examId": exam_id})
    else:
        raise APIException(UPDATE_ERROR)


@exam_blueprint.route("/score/search", methods=["POST"])
@user_is_login
def exam_score_search():
    """
    查询考试成绩
    :return:
    """
    data = get_data()
    username = session.get("username")
    exam_id = data.get("examId")
    user = User.query.filter(User.username == username).limit(1).first()
    exam = Exam.query.filter(Exam.exam_id == exam_id).limit(1).first()
    r = list()
    questions = list()
    count = {
        1: 0,
        2: 0,
        3: 0,
        4: 0,
        5: 0,
        6: 0,
        "all": 0
    }

    if exam is None:
        raise APIException(NO_EXAM_EXISTS)
    paper = Paper.query.filter(Paper.paper_id == exam.paper_id).limit(1).first()
    if paper is None:
        raise APIException(NO_PAPER_EXISTS)
    for d in json.loads(paper.paper_question):
        question = Question.query.filter_by(question_id=d.get("question")).limit(1).first()
        question_type = question.question_type
        questions.append({
            "question_id": d.get("question"),
            "question_type": question_type
        })
        count[question_type] += d.get("score")
        count["all"] += d.get("score")

    if 2 <= user.user_type <= 4:
        student_exams = StudentExam.query.filter_by(exam_id=exam_id).all()
        for student_exam in student_exams:
            tmp = dict({
                1: 0,
                2: 0,
                3: 0,
                4: 0,
                5: 0,
                6: 0,
                "all": 0
            })
            tmp["student_exam_id"] = student_exam.student_exam_id
            tmp["exam_name"] = exam.exam_name
            tmp["score"] = student_exam.score
            tmp["username"] = student_exam.student_id
            for question in questions:
                user_submit = UserSubmit.query.filter(UserSubmit.user_id == student_exam.student_id,
                                                      UserSubmit.question_id == question.get("question_id")).limit(1).first()
                if user_submit is not None:
                    tmp[question.get("question_type")] += user_submit.result
                    tmp["all"] += user_submit.result
                else:
                    tmp[question.get("question_type")] = 0
            for i in range(1, 7):
                tmp[i] = str(tmp[i]) + str("/") + str(count[i])
            r.append(tmp)
    elif user.user_type == 1:
        student_exam = StudentHomework.query.filter_by(student_id=username, exam_id=exam_id).first()
        if student_exam is None:
            raise APIException(SEARCH_ERROR)
        r.append({"score": student_exam.score})

    return response(200, "Query Score Success", r)


@exam_blueprint.route("/score/export", methods=["POST"])
@user_is_login
@check_user_type_2
def exam_score_export():
    """
    导出考试成绩
    :return:
    """
    data = get_data()
    username = session.get("username")
    exam_id = data.get("examId")
    user = User.query.filter(User.username == username).first()
    exam = Exam.query.filter(Exam.exam_id == exam_id).first()
    r = {
        "username": [],
        "score": []
    }

    if exam is None:
        raise APIException(NO_HOMEWORK_EXISTS)

    if 2 == user.user_type and exam.exam_add_user_id != int(username):
        raise APIException(USERNAME_ILLEGAL)
    else:
        student_exams = StudentExam.query.filter(StudentExam.exam_id == exam_id).all()
        for student_exam in student_exams:
            r["username"].append(student_exam.student_id)
            r["score"].append(student_exam.score)

    result = pd.DataFrame(r)
    output = BytesIO()
    writer = pd.ExcelWriter(output, engine='xlsxwriter')

    result.to_excel(writer, startrow=0, merge_cells=False, sheet_name="Result")

    workbook = writer.book
    my_format = workbook.add_format()
    my_format.set_bg_color('#eeeeee')

    writer.close()
    output.seek(0)

    return send_file(output, attachment_filename="result.xlsx", as_attachment=True)


@exam_blueprint.route("/score/update", methods=["POST"])
@user_is_login
@check_user_type_2
def exam_score_update():
    """
    修改考试成绩
    :return:
    """
    data = get_data()
    student_exam_id = data.get("studentExamId")
    score = data.get("score")

    if score is None:
        raise APIException(NO_DATA_FOUND)
    student_exam = StudentExam.query.filter(StudentExam.student_exam_id == student_exam_id).first()
    if student_exam is None:
        raise APIException(NO_DATA_FOUND)

    res = StudentExam.query.filter(StudentExam.student_exam_id == student_exam_id).update(
        {"score": score}
    )
    db.session.commit()
    db.session.close()
    if res == 1:
        return response(200, "Update Score Success", {})
    else:
        raise APIException(UPDATE_ERROR)


# @exam_blueprint.route("/answer", methods=["GET"])
# def exam_answer():
#     """
#     考试答案
#     :return:
#     """
#     # TODO
#     pass
#

@exam_blueprint.route("/search", methods=["POST"], endpoint="examSearch")
@user_is_login
def exam_search():
    """
    进入单场考试
    :return:
    """
    data = get_data()
    r = {
        "questions": {
            "FillBlank": [],
            "Select": [],
            "Judge": [],
            "ProgramFillBlank": [],
            "ProgramModify": [],
            "Program": []
        }
    }
    # red = Redis()
    exam_id = data.get("examId")
    username = session.get("username")
    user = User.query.filter_by(username=username).limit(1).first()
    exam = Exam.query.filter_by(exam_id=exam_id).limit(1).first()
    if exam is None:
        raise APIException(NO_EXAM_EXISTS)
    paper = Paper.query.filter(Paper.paper_id == exam.paper_id).limit(1).first()
    if paper is None:
        raise APIException(NO_PAPER_EXISTS)
    if user.user_type == 1:
        student_exam = StudentExam.query.filter_by(exam_id=exam_id, student_id=username).first()
        if student_exam is None:
            raise APIException(USERNAME_ILLEGAL)

    for questions in json.loads(paper.paper_question):
        question_id = questions.get("question")
        score = questions.get("score")
        ret = dict()

        tmp_question = Question.query.filter_by(question_id=question_id).limit(1).first()
        if tmp_question is None:
            raise APIException(NO_QUESTION_EXISTS)
        question_type = tmp_question.question_type
        # 填空题
        if question_type == 1:
            question = QuestionOfFillBlank.query.filter_by(question_id=question_id).first()
        # 选择题
        elif question_type == 2:
            question = QuestionOfSelect.query.filter_by(question_id=question_id).first()
            l = json.loads(question.wrong_answer)
            l.append(question.right_answer)
            random.shuffle(l)
            ret["options"] = l
        # 判断题
        elif question_type == 3:
            question = QuestionOfJudge.query.filter_by(question_id=question_id).first()
        # 程序填空题
        elif question_type == 4:
            question = QuestionOfProgramFillBlank.query.filter_by(question_id=question_id).first()
            ret["inputFormat"] = question.input_format[: 3]
            ret["outputFormat"] = question.output_format[: 3]
        # 程序改错题
        elif question_type == 5:
            question = QuestionOfProgramModify.query.filter_by(question_id=question_id).first()
            ret["inputFormat"] = question.input_format[: 3]
            ret["outputFormat"] = question.output_format[: 3]
        # 编程题
        elif question_type == 6:
            question = QuestionOfProgram.query.filter_by(question_id=question_id).first()
            ret["inputFormat"] = question.input_format[: 3]
            ret["outputFormat"] = question.output_format[: 3]

        data = {
            "questionId": question_id,
            "questionTitle": question.question_title,
            "score": score if score is not None else 0
        }
        if question_type == 1:
            r["questions"]["FillBlank"].append(data)
        elif question_type == 2:
            r["questions"]["Select"].append(data)
        elif question_type == 3:
            r["questions"]["Judge"].append(data)
        elif question_type == 4:
            r["questions"]["ProgramFillBlank"].append(data)
        elif question_type == 5:
            r["questions"]["ProgramModify"].append(data)
        elif question_type == 6:
            r["questions"]["Program"].append(data)

        # if red.get_data(username) is None:
        #     red.set_data(username)
        # else:
        #     raise APIException(NEED_UNLOCK_SECRET)

    r["exam_id"] = exam.exam_id
    r["paper_id"] = exam.paper_id
    r["unlock_secret"] = exam.unlock_secret
    r["exam_name"] = exam.exam_name
    r["start_time"] = str(exam.start_time)
    r["end_time"] = str(exam.end_time)
    r["create_time"] = str(exam.create_time)

    return response(200, "Search Exam Success", r)


# @exam_blueprint.route("/unlock", methods=["GET", "POST"])
# @user_is_login
# def exam_unlock():
#     """
#     解锁 用户
#     :return:
#     """
#     red = Redis()
#     data = request.json
#     exam_id = data.get("examId")
#     unlock_secret = data.get("unlockSecret")
#     username = session.get("username")
#     user = User.query.filter_by(username=username).first()
#     if user is None:
#         raise APIException(USERNAME_NOT_EXISTS)
#     exam = Exam.query.filter_by(exam_id=exam_id).first()
#     if exam is None:
#         raise APIException(NO_EXAM_EXISTS)
#
#     if red.get_data(username) is None:
#         raise APIException(NO_UNLOCK_SECRET_NEED)
#     if unlock_secret == exam.unlock_secret:
#         red.del_data(username)
#         return redirect(url_for("exam.examSearch", examId=exam_id))
#     else:
#         raise APIException(UNLOCK_SECRET_ERROR)



@exam_blueprint.route("/list/course", methods=["POST"])
@user_is_login
def exam_list_course():
    """
    获取考试列表
    :return:
    """
    try:
        current_page = int(request.args.get("currentPage"))
    except:
        current_page = 1
    data = get_data()
    course_id = data.get("courseId")
    data_per_page = 10
    username = session.get("username")
    user = User.query.filter(User.username == username).limit(1).first()
    r = list()

    if user.user_type == 1:
        exams = Exam.query.filter_by(status=True, relate_course=course_id).order_by(-Exam.create_time).all()
        list_len = len(exams)
        for exam in exams:
            tmp = dict()
            tmp["exam_id"] = exam.exam_id
            tmp["exam_name"] = exam.exam_name
            tmp["paper_id"] = exam.paper_id
            tmp["exam_add_user_id"] = exam.exam_add_user_id
            tmp["start_time"] = str(exam.start_time)
            tmp["end_time"] = str(exam.end_time)
            tmp["state"] = "Open" if exam.start_time <= datetime.datetime.now() <= exam.end_time else "Ended"
            tmp["status"] = 1
            r.append(tmp)
    else:
        exams = Exam.query.filter_by(relate_course=course_id).order_by(-Exam.create_time).all()
        list_len = len(exams)
        for exam in exams:
            tmp = dict()
            tmp["exam_id"] = exam.exam_id
            tmp["exam_name"] = exam.exam_name
            tmp["paper_id"] = exam.paper_id
            tmp["exam_add_user_id"] = exam.exam_add_user_id
            tmp["start_time"] = str(exam.start_time)
            tmp["end_time"] = str(exam.end_time)
            tmp["state"] = "Open" if exam.start_time <= datetime.datetime.now() <= exam.end_time else "Ended"
            tmp["status"] = exam.status
            r.append(tmp)
    max_page_num = ceil(list_len / data_per_page)
    ret = {
        "currentPage": current_page,
        "maxPageNum": max_page_num,
        "num": list_len,
        "list": r[data_per_page * (current_page - 1): data_per_page * current_page]
    }

    return response(200, "Query Exam Success", ret)
